<?php
/**
* 微信支付构造类
*/
header("Content-Type:text/html;charset=utf-8");
class spWxPay
{
    //公众账号应用Id
    const APPID                    =    'wx2e6769bcd88fcc34';
    //商户ID
    const MERCHANTID              = '1486937292';
    //API签名密钥
    const SIGNKEY = '5d1b42980ad3a3acfaed7790a2a076b4';
    
    //统一下单接口
    const UNIFIEDORDER            =    'https://api.mch.weixin.qq.com/pay/unifiedorder';                
    //微信异步回调WEB端通知地址
    const WEB_NOTIFYURL            =    'http://quntui.phoenixfm.cn/callback/notify.php';
    
    //查询订单接口
    const ORDERQUERY            =    'https://api.mch.weixin.qq.com/pay/orderquery';
    //申请退款接口
    const REFUND                =    'https://api.mch.weixin.qq.com/secapi/pay/refund';
    //查询退款接口
    const REFUNDQUERY            =    'https://api.mch.weixin.qq.com/pay/refundquery';
    //=======【证书路径设置】=====================================
    /**
     * TODO：设置商户证书路径
     * 证书路径,注意应该填写绝对路径（仅退款、撤销订单时需要，可登录商户平台下载，
     * API证书下载地址：https://pay.weixin.qq.com/index.php/account/api_cert，下载之前需要安装商户操作证书）
     * @var path
     */
    const SSLCERT_PATH             =     '/data/ifengsite/htdocs/diantai/web/Public/cert/apiclient_cert.pem';
    const SSLKEY_PATH             =     '/data/ifengsite/htdocs/diantai/web/Public/cert/apiclient_key.pem';
    
    /**
     * 请求统一下单操作
     * @param array $p_data            //交易订单数据
     * @param array $m_tradeInfo       //商户交易配置信息
     * @return Ambigous <mixed, void, NULL, multitype:>|Ambigous <$arr, boolean, mixed>
     */
    function unifiedorder($tradeData){
        $setData['appid'] = self::APPID;    //公众账号ID/应用ID
        $setData['mch_id'] = self::MERCHANTID;    //网页应用商户号/客户端应用商户号
        $setData['nonce_str'] = self::getNonceStr();    //随机字符串
        $setData['sign_type'] = 'MD5';
        $setData['body'] = $tradeData['p_name'];    //商品描述
        $setData['attach'] = $tradeData['attach'];    //附加数据
        $setData['out_trade_no'] = $tradeData['tradeId'];    //商户订单号
        $setData['total_fee'] = $tradeData['o_amount'];    //总金额 单位为分
        $setData['spbill_create_ip'] = $tradeData['ip'];    //终端IP
        $setData['notify_url'] = self::WEB_NOTIFYURL;    //异步接收通知地址
        $setData['trade_type'] = 'NATIVE';    //交易类型 JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付，统一下单接口trade_type的传参可参考这里
        $sign = self::MakeSign($setData);
        $setData['sign'] =$sign;     //获取签名生成算法方法
        $logObj = spClass('spLog');
        $logObj->log('交易订单数据'.implode(',',$setData), 'INFO');
        //将数组转换为XML字符
        $xml = self::arrToXml($setData);
		$xml_result = self::postXmlCurl($xml, self::UNIFIEDORDER);
      	$logObj->log('发起微信统一下单返回结果'.$xml_result, 'INFO');
        // 发起统一下单请求
        $result =  self::xmlToArr($xml_result);
        
        if($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS'){
            
            //发起统一 下单时生成的sign 与 返回预支付交易结果中的sign 作校验
            if(empty($result['sign'])){
                $logObj->log('预交易结果中没有签名字段', 'ERROR');
                return false;
            }
            //验证签名
            $returnRes['return_code'] = $result['return_code'];
            $returnRes['return_msg'] = $result['return_msg'];
            $returnRes['appid'] = $result['appid'];
            $returnRes['mch_id'] = $result['mch_id'];
            $returnRes['nonce_str'] = $result['nonce_str'];
            $returnRes['result_code'] = $result['result_code'];
            $returnRes['prepay_id'] = $result['prepay_id'];
            $returnRes['trade_type'] = $result['trade_type'];
            $returnRes['code_url'] = $result['code_url'];
            $returnSign = self::MakeSign($returnRes);
            if($result['sign'] == $returnSign){
                //区分返回数据
                $logObj->log('生成预支付订单成功', 'INFO'); 
                return $result;
                
            }else{        
                $logObj->log('下单时生成的sign 与 返回预支付交易结果中的sign 作校验 不相同 发起时：'.$sign.' === 接收到的：'.$result['sign'].'', 'ERROR');
                return false;          
            }
             
        }else{
            $logObj->log('统一下单接口调用失败，返回结果：'.implode(',',$result), 'ERROR');
            return false;
        }
    }
  	/**
    * 查询订单状态
    *  
    * @param mixed $orderData
    */
    function queryOrder($orderData){
        $setData['appid'] = self::APPID;    //公众账号ID/应用ID
        $setData['mch_id'] = self::MERCHANTID;    //网页应用商户号/客户端应用商户号
        //$setData['transaction_id']=$orderData[''];  //微信的订单编号
        $setData['out_trade_no']=$orderData['out_trade_no'];    //商户自己的订单号
        $setData['nonce_str']=$orderData['nonce_str'];       //随机字符串
        $setData['sign_type']='MD5';       //签名算法
        $setData['sign']=self::MakeSign($setData);            //签名
        $xml = self::arrToXml($setData);

        $xml_result = self::postXmlCurl($xml, self::ORDERQUERY);
        $logObj->log('查询订单返回结果：'.$xml_result, 'INFO');
        $result =  self::xmlToArr($xml_result);
        if($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS'){
            //订单状态不一定是成功
            $logObj->log('查询订单状态成功', 'INFO'); 
            return $result;
        }else{
            $logObj->log('查询订单失败', 'ERROR');
            return false;
        }
        
    }
    /**
     * 产生随机字符串，不长于32位
     * @param int $length
     * @return 产生的随机字符串
     */
    function getNonceStr($length = 32) {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str ="";
    
        for($i= 0; $i < $length; $i++){
                
            $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
    
        }
        return $str;
    }
    /**
     * 生成签名
     * @param WxPayConfigInterface $config  配置对象
     * @param bool $needSignType  是否需要补signtype
     * @return 签名，本函数不覆盖sign成员变量，如要设置签名需要调用SetSign方法赋值
     */
    public function MakeSign($setData,$GetSignType='MD5')
    {
        $logObj = spClass('spLog');
        //签名步骤一：按字典序排序参数
        ksort($setData);
        $string = self::ToUrlParams($setData);
        //签名步骤二：在string后加入KEY
        $string = $string . "&key=".self::SIGNKEY;
        //签名步骤三：MD5加密或者HMAC-SHA256
        if($GetSignType == "MD5"){
            $logObj->log('待加密字符串;'.$string);
            $string = md5($string);
        } else if($GetSignType == "HMAC-SHA256") {
            $string = hash_hmac("sha256",$string ,self::SIGNKEY);
        } else {
            return false;
        }         
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    } 
    /**
     * 格式化参数格式化成url参数
     */
    public function ToUrlParams($setData)
    {
        $buff = "";
        foreach ($setData as $k => $v)
        {
            if($k != "sign" && $v != "" && !is_array($v)){
                $buff .= $k . "=" . $v . "&";
            }
        }
        
        $buff = trim($buff, "&");
        return $buff;
    }
    /**
     * 将数组转换为XML字符
     * @param $arr Array 数组
     * @return $xml XmlString 由数组转换为XML字符
     */
    function arrToXml($arr){
        if(!is_array($arr) || count($arr) <= 0){
            return false;
        }

        $xml = '<xml>';
        foreach ($arr as $key => $val){

            if(is_numeric($val)){
                $xml.= '<'.$key.'>'.$val.'</'.$key.'>';
            }
            else{
                $xml.= '<'.$key.'><![CDATA['.$val.']]></'.$key.'>';
            }

        }
        $xml.= '</xml>';
        return $xml;
    }
    /**
     * 将XML字符转换为数组
     * @param $xml xmlString    XML字符
     * @return $arr Array 由XML字符转换为数组数据
     */
    function xmlToArr($xml){
        $xml = @trim($xml);
        if(empty($xml) || strtolower($xml) == "null"){
            return false;
        }

        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $arr = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $arr;
    }
    /**
     * 以post方式提交xml到对应的接口url
     * @param string $xml  需要post的xml数据
     * @param string $url  url
     * @param bool $useCert 是否需要证书，默认不需要
     * @param int $second   url执行超时时间，默认30s
     * @return
     */
    private function postXmlCurl($xml, $url, $useCert = false, $second = 30){
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TRUE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);    //严格校验
        
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        
        if($useCert == true){
            //设置证书
            //使用证书：cert 与 key 分别属于两个.pem文件
            curl_setopt($ch,CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT, self::SSLCERT_PATH);
            curl_setopt($ch,CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY, self::SSLKEY_PATH);
        }
        
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        
        //运行curl
        $data = curl_exec($ch);
        //返回结果
        if(!empty($data)){
            curl_close($ch);
            return $data;
        }else{
            $error = curl_errno($ch);
            curl_close($ch);
            return false;
        }
    }  
}
